// exe_lidar_ratio.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <list>
#include <cmath>
#include <numeric>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <quigon.h>

#ifdef MACRO_STK
#include "stk_para.h"
#include "stk_lidar.h"
#include "stk_lidar_io.h"
#endif //MACRO_STK

#ifdef MACRO_YL
#include "stk_para.h"
#include "yl_lidar.h"
#include "yl_lidar_io.h"
#endif //MACRO_YL

#include "basic_process.h"
#include "noise_filter.h"
#include "fernald.h"
#include "output_lidarpro.h"


using namespace std;
using namespace boost::posix_time;
using namespace quigon;

#ifdef MACRO_STK
typedef STK_Para Para;
typedef STK_Line Line;
const string para_file = "../settings/stk_lidar_para.txt";
const string prefix = "stk";
const vector<string> &aver_attr = stk_aver_attr;
#endif  //MACRO_STK

#ifdef MACRO_YL
typedef YL_Para Para;
typedef YL_Line Line;
const string para_file = "../settings/yl_lidar_para.txt";
const string prefix = "yl";
const vector<string> &aver_attr = yl_aver_attr;
#endif  //MACRO_YL

//lidar_ratio start_t, end_t, aod, low_dist, high_dist
int main(int argc, char* argv[])
{
	string start_t, end_t;
	double aod;
	double limit_dist;
	if (argc < 5)
	{
		cout << "usage: ***_lidar_ratio yyyymmddThhmmss yyyymmddThhmmss aod limit_dist" << endl;
		cout << "The capital 'T' in the 'iso time string' is optional" << endl;
		exit(0);
	}
	else
	{
		start_t = argv[1];
		end_t = argv[2];
		aod = atof(argv[3]);
		limit_dist = atof(argv[4]);
	}

	// Check the date_time format, if not iso string, add 'T' into it.
	if (start_t[8] != 'T')
		start_t.insert(8, 1, 'T');
	if (end_t[8] != 'T')
		end_t.insert(8, 1, 'T');

	// Load lidar parameters
	Para para(para_file);

	// Load proper temperature and pressure "profiles"
	// to calculate backscatter coefficient of atmosphere molecules: beta_m
	Atmos_Profile p_prof = load_atmos_profile("../settings/atmos_profiles/US_1976.p_profile", para);
	Atmos_Profile t_prof = load_atmos_profile("../settings/atmos_profiles/US_1976.t_profile", para);
	Atmos_Profile beta_m = cal_beta_m(t_prof, p_prof, 523.0, 0.0284);

	size_t limit_index = para.distance_i[limit_dist];

	// Loading the priori data
//  cout << "Loading priori data..." << endl;
//  Time_Serie fe_distance("../settings/lidar/stk_priori_distance.txt");
//  Time_Serie fe_value("../settings/lidar/stk_priori_value.txt");

	// Making a container for the data
	list<Line> container;

	// import the data.
	cout << "Importing data..." << endl;
	import(
	        container,
	        from_iso_string(start_t),       // boost::posix_time::from_iso_string
	        from_iso_string(end_t),     // take care of the "T" in the string
	        para
	);

	cout << "lines num: " << container.size() << endl;

	cout << "Making an averager" << endl;
	time_duration aver_duration = from_iso_string(end_t) - from_iso_string(start_t);
	double aver_seconds = aver_duration.total_milliseconds() / 1000.0;
	Average_Machine<Line> aver(aver_seconds, aver_attr);
	// Make a functor for correction, and more options are available
	// e.g. Correction<STK_Line> my_corr(para, Status::BACKGROUND | Status::AFTERPULSE | Status::DISTANCE);
	// default is doing every possible correction
	cout << "Making a corrector" << endl;
	Correction<Line, Para> corr(para, Status::NORMALIZE | Status::ENERGY, false);

	// Make a functor for filling the lowest unseen area.
	// here 5 means use the average of lowest 5 known data to fill the unknown area.
	cout << "Making a filler" << endl;
	Fill_Lowest filler(para, 5);

	// Make a filter
	cout << "Making a filter ..." << endl;
	Serie<string, double> filter_para("../settings/lidar/stk_filter.txt");
	Noise_Filter_Proto filter(
	        filter_para["low_index"],
	        filter_para["high_index"],
	        filter_para["low_buddy_num"],
	        filter_para["high_buddy_num"],
	        para.zero_meter_index,
	        para.size - 1);
	double trigger_value = filter_para["trigger_value"];
	size_t lowest_trigger_index = filter_para["lowest_trigger_index"];

	aver(container);
	Line the_line = container.front();
	the_line.resize(limit_index + 1);
	corr(the_line);
	filler(the_line);
	filter(the_line, trigger_value, lowest_trigger_index);
	kick_minus(the_line);

	cout << "Making a retriever" << endl;
	Fernald<Line, Para> fernald(para, beta_m, 60.0, -999.9, -999.9, false);

	// AOD sa pairs..
	Serie<double, double> test_result;
	// using general value to store the minimum (new_aod - aod)
	test_result.edit_general_value(1.0);
	double last_aod;
	bool inited = false;

	cout << setw(16) << "testing sa" << setw(16) << "result aod" << setw(16) << "real aod" << endl;
	for (double test_sa = 10.0; test_sa <= 75.0; test_sa += 0.1)
	{
		Line the_copy(the_line);
		fernald.get_sa() = test_sa;
		fernald(the_copy);

		double new_aod = accumulate(the_copy.begin(), the_copy.end(), 0.0) * the_copy["bin_size"];
		if (!inited)
		{
			last_aod = new_aod;
			inited = true;
		}
		cout << setw(16) << test_sa << setw(16) << new_aod << setw(16) << aod << endl;

		if (fabs(new_aod - last_aod) > 0.5)
			break;
		else
		{
			test_result.add_entry(new_aod, test_sa);
			last_aod = new_aod;

			double new_diff = fabs(new_aod - aod);
			if (new_diff < test_result())
				test_result.edit_general_value(new_diff);
		}
	}
	double result = test_result.interp_val(aod);
	cout << "=== Result: " << result;
	if (test_result() > 0.1)
		cout << "  !!! Warning: The result is not reliable. Not saving" << endl;

	else
	{
		cout << endl;

		string filename = string("../settings/calibration/") + prefix + "_lidar_ratio.txt";
		Time_Serie database;
		if (!database.load(filename))
		{
			cout << filename << " doesn't exist. creating new" << endl;
		}
		database.add_entry(from_iso_string(start_t), result);
		database.save(filename);
	}
	return 0;
}


